package cn.zxh.improvement.common.core.utils;

import cn.zxh.improvement.common.core.exceptions.RuntimeWrappedException;
import io.vavr.Tuple2;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * 异常工具类
 * 解析堆栈
 *
 * @author zxh
 * @since 2023/4/26 15:29:31
 */
public class Exceptions {

    /**
     * 转换异常为运行时异常
     *
     * @param t throwable
     * @return runtime exception
     */
    public static RuntimeException toRuntime(Throwable t) {
        return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeWrappedException(t);
    }

    /**
     * 递归逆向打印堆栈及cause(即从最底层的异常开始往上打)
     * @param t 原始异常
     * @param causeDepth 需要递归打印的cause的最大深度
     * @param counter 当前打印的cause的深度计数器(这里必须用引用类型，如果用基本数据类型，你对计数器的修改只能对当前栈帧可见，但是这个计数器，又必须在所有栈帧中可见，所以只能用引用类型)
     * @param stackDepth 每一个异常栈的打印深度
     * @param sb 字符串构造器
     */
    public static void recursiveReversePrintStackCause(Throwable t, int causeDepth, ForwardCounter counter, int stackDepth, StringBuilder sb){
        if(t == null){
            return;
        }
        if (t.getCause() != null){
            recursiveReversePrintStackCause(t.getCause(), causeDepth, counter, stackDepth, sb);
        }
        if(counter.i++ < causeDepth){
            getPrintStack(t, stackDepth, sb);
        }
    }

    public static String getPrintStack(Throwable t, int stackDepth, StringBuilder sb){
        StackTraceElement[] stackTraceElements = t.getStackTrace();
        if(sb.lastIndexOf("\t") > -1){
            sb.deleteCharAt(sb.length()-1);
            sb.append("Caused: ");
        }
        sb.append(t.getClass().getName()).append(": ").append(t.getMessage()).append("\n\t");
        for(int i=0; i < stackDepth; ++i){
            if(i >= stackTraceElements.length){
                break;
            }
            StackTraceElement element = stackTraceElements[i];
            sb.append(reduceClassName(element.getClassName()))
                    .append("#")
                    .append(element.getMethodName())
                    .append(":")
                    .append(element.getLineNumber())
                    .append("\n\t");
        }
        return sb.toString();
    }

    public static String reduceClassName(String className) {
        String[] split = className.split("\\.");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < split.length - 1; i++) {
            String s = split[i];
            sb.append(s.charAt(0)).append(".");
        }
        sb.append(split[split.length - 1]);
        return sb.toString();
    }

    public static class ForwardCounter {
        int i = 0;

        public static ForwardCounter creator() {
            return new ForwardCounter();
        }
    }

    /**
     * 跟踪异常
     * 返回所有的日志过程
     *
     * @param t throwable
     * @return trace list
     */
    public static List<String> getTrace(Throwable t) {
        return getTrace(t, t::toString);
    }

    /**
     * 使用自定义消息获取可抛出跟踪
     *
     * @param t           throwable
     * @param msgSupplier custom message supplier
     * @return trace list
     */
    public static List<String> getTrace(Throwable t, Supplier<String> msgSupplier) {
        ArrayList<String> trace = new ArrayList<String>();
        getTrace(t, null, trace, msgSupplier);
        return trace;
    }

    private static void getTrace(Throwable t, StackTraceElement[] enclosingTrace, List<String> trace,
                                 Supplier<String> msgSupplier) {

        // add message
        String msg = msgSupplier == null ? t.toString() : msgSupplier.get();
        trace.add(msg);

        // If debug enabled, log error stack trace
        Tuple2<StackTraceElement[], Integer> stack = forEachStackTrace(t, enclosingTrace, ste -> {
            trace.add("\tat " + ste);
        });

        // if there are remaining lines, print remaining information
        if (stack._2() > 0) {
            trace.add("\tat ... " + stack._2() + " more");
        }

        // print cause, if any
        Throwable cause = t.getCause();
        if (cause != null) {
            getTrace(cause, stack._1(), trace, () -> "Caused by: " + cause.toString());
        }
    }

    private static Tuple2<StackTraceElement[], Integer> forEachStackTrace(Throwable t,
                                                                          StackTraceElement[] enclosingTrace, Consumer<StackTraceElement> s) {
        StackTraceElement[] stes = t.getStackTrace();
        int framesInCommon = 0;
        if (stes != null && stes.length > 0) {
            // find max length
            int m = stes.length - 1;
            if (enclosingTrace != null) {
                int n = enclosingTrace.length - 1;
                while (m >= 0 && n >= 0 && stes[m].equals(enclosingTrace[n])) {
                    m--;
                    n--;
                }
            }
            // Ignore common part
            framesInCommon = stes.length - 1 - m;

            // print special parts in this stack
            for (int i = 0; i <= m; i++) {
                String className = stes[i].getClassName();

                // ignore the stack inside exception
                try {
                    Class<?> stackClazz = Class.forName(className);
                    if (Throwable.class.isAssignableFrom(stackClazz)) {
                        continue;
                    }
                } catch (Exception e) {
                    // ignore this exception
                }

                // Throw exception
                s.accept(stes[i]);
            }
        }
        return new Tuple2<>(stes, framesInCommon);
    }

}
